/*
 * Copyright (c) 2023 emulex Technologies Co., Ltd.
 * openUBMC is licensed under Mulan PSL v2.
 * You can use this software according to the terms and conditions of the Mulan PSL v2.
 * You may obtain a copy of Mulan PSL v2 at:
 *         http://license.coscl.org.cn/MulanPSL2
 * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
 * EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
 * MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
 * See the Mulan PSL v2 for more details.
 */

#include "emulex_card.h"
#include <cstdio>
#include <mc/log.h>

namespace dev {

emulex_card::emulex_card() {
    
}

emulex_card::~emulex_card() {

}

void emulex_card::init_network_ports() {
    auto objects = get_children();
    if (objects.empty()) {
        elog("Error: No network ports found");
        return;
    }
    for (auto& object : objects) {
        auto object_name = object->get_name();
        if (object_name.substr(0, object_name.find_first_of("_")) == "NicPort") {
            elog("emulex_card init network port ${name}", ("name", object_name));
            m_network_ports.push_back(static_cast<emulex_port*>(object.get()));
        }
    }
}

bool emulex_card::start() {
    init_network_ports();
    for (auto& port : m_network_ports) {
        port->start();
    }
    start_protocol();
    init_imu_protocol();

    m_network_adapter.register_os_reset_callback([this]() {
        this->handle_os_reset();
    });
    return true;
}

bool emulex_card::stop() {

    return true;
}

// 注册回调，框架进行动态属性替换，用于配置时的参数替换
void emulex_card::replace_dynamic_property(mc::mutable_dict& object, const mc::dict& connector) {
    m_pcie_device.replace_dynamic_property(object, connector);
    m_board.replace_dynamic_property(object, connector);
}

bool emulex_card::init(mc::mutable_dict& csr_object, const mc::dict& connector) {
    try {
        from_variant(csr_object, *this);
    } catch (const std::exception& e) {
        elog("emulex_card init failed, exception: ${exception}", ("exception", e.what()));
        return false;
    }
    return true;
}

bool emulex_card::start_pldm_protocol() {
    if ((m_pcie_device.DevBus == 0) && (m_pcie_device.DevDevice == 0) &&
        (m_pcie_device.DevFunction == 0)) {
        dlog("PCIe device bdf not initialized");
        return false;
    }
    elog("start_pldm_protocol PCIe device bdf(${bus}:${device}:${function}) ",
        ("bus", m_pcie_device.DevBus)("device", m_pcie_device.DevDevice)("function", m_pcie_device.DevFunction));
    uint16_t phy_addr = mctp::init_phy_addr(m_pcie_device.DevBus, m_pcie_device.DevDevice,
                                            m_pcie_device.DevFunction);

    m_mctp_object = new mctp(this, phy_addr, MCTP_MESSAGE_TYPE::MCTP_MESSAGE_TYPE_PLDM, "");

    auto pldm_update_task = [this]() {
        m_pldm_over_mctp_emulex = std::make_shared<pldm_over_mctp_emulex>(*m_mctp_object);


        // 避免重复发送命令，定时器更新fru的缓存，interface中定期从缓存同步数据
        m_pldm_over_mctp_emulex->update_all_fru_records();
        m_pldm_timer            = new mc::core::timer(this);
        m_pldm_timer->timeout.connect([this]() {
            try {
                if (m_pldm_over_mctp_emulex) {
                    m_pldm_over_mctp_emulex->update_all_fru_records();
                }
            } catch (const std::exception& e) {
                ilog("Exception in update fru records: ${error}", ("error", e.what()));
            }
        });
        m_pldm_timer->set_single_shot(false);
        m_pldm_timer->start(mc::milliseconds(60000));

        
        for (auto& port : m_network_ports) {
            port->start_pldm_protocol(m_pldm_over_mctp_emulex, m_interval);
        }
        
        m_pcie_card.start_pldm_update_task_emulex(m_pldm_over_mctp_emulex, mc::milliseconds(60000));
        m_network_adapter.start_pldm_update_task_emulex(m_pldm_over_mctp_emulex, mc::milliseconds(60000));
        m_network_adapter_cooling.start_pldm_update_task_emulex(m_pldm_over_mctp_emulex,m_interval);
        
        
    };
    m_mctp_object->create_transport_and_endpoint(std::string(get_object_name()), pldm_update_task);
    return true;
}

bool emulex_card::start_protocol() {
    if (!start_pldm_protocol()) {
        m_pcie_device.pcie_device_bdf_changed.connect(
            [this](uint8_t bus, uint8_t device, uint8_t function) {
                elog("PCIe device bdf(${bus}:${device}:${function}) changed, start pldm protocol",
                     ("bus", bus)("device", device)("function", function));
                start_pldm_protocol();
            });
        return false;
    }
    return true;
}

void emulex_card::update_ports_bdf_async(imu_pcie_device_req_t& pcie_device_info) {
    for (auto& port : m_network_ports) {
        bool is_success = port->update_port_bdf(pcie_device_info);
        if (!is_success) {
            elog("Error: Failed to update port(${port_id}) BDF",
                 ("port_id", static_cast<uint32_t>(port->m_network_port.PortId)));
        }
    }
}

bool emulex_card::init_imu_protocol() {
    try {
        m_imu_obj = std::make_shared<imu>(get_service());
        ilog("IMU protocol initialized successfully");
        return true;
    } catch (const std::exception& e) {
        elog("Failed to initialize IMU protocol: ${error}", ("error", e.what()));
        return false;
    }
}

void emulex_card::stop_pldm_protocol() {
    if (m_pldm_timer) {
        m_pldm_timer->stop();
    }
    m_pcie_card.stop_pldm_update_task();
    m_network_adapter.stop_pldm_update_task();
    m_network_adapter_cooling.stop_pldm_update_task();

    // 停止所有端口的pldm任务
    for (auto& port : m_network_ports) {
        port->stop_pldm_protocol();
    }
    // 清空endpoint
    if (m_mctp_object) {
        delete m_mctp_object;
        m_mctp_object = nullptr;
    }

}

void emulex_card::handle_os_reset() {
    elog("${name} restart pldm protocol due to OS reset", ("name", m_pcie_device.DeviceName.get_value().as<std::string>()));
    stop_pldm_protocol();
    start_pldm_protocol();
    elog("${name} restart pldm protocol end", ("name", m_pcie_device.DeviceName.get_value().as<std::string>()));
}


} // namespace dev

MC_REFLECT(dev::emulex_card,
           ((m_system_id, "SystemId"))((m_pcie_device, "bmc.dev.PCIeDevice"))(
               (m_pcie_card, "bmc.dev.PCIeCard"))((m_network_adapter, "bmc.dev.NetworkAdapter"))(
               (m_board, "bmc.dev.Board"))((m_network_adapter_cooling,
                                            "bmc.dev.NetworkAdapter.Cooling"))((m_pcie_device_bandwidth, "PCIeDevice.Bandwidth")))